﻿using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Security.Cryptography;

namespace H.PK
{
    internal class HEncryption
    {
        /// <summary>
        /// 存储密钥
        /// </summary>
        private readonly RijndaelManaged _RijndaelManaged;

        /// <summary>
        /// 键长度(key+iv的字节长度)
        /// </summary>
        public const int KeySize = 32;

        /// <summary>
        /// 获取当前用于加密界面的key
        /// </summary>
        /// <returns></returns>
        public byte[] GetKey()
        {
            byte[] keys = _RijndaelManaged.Key.Concat(_RijndaelManaged.IV).ToArray();
            XorBytes(keys, 1, keys.Length - 1, keys[0]);
            return keys;
        }

        /// <summary>
        /// 异或数组中的各个位
        /// </summary>
        /// <param name="bytes">被异或的字节数组</param>
        /// <param name="startIndex">开始处理的下标</param>
        /// <param name="length">希望处理的字节个数</param>
        /// <param name="key">密钥</param>
        public static void XorBytes(byte[] bytes, int startIndex, int length, byte key)
        {
            for (int i = startIndex, j = startIndex + length; i < j; i++)
            {
                bytes[i] ^= key;
            }
        }

        public HEncryption()
        {
            _RijndaelManaged = new RijndaelManaged
            {
                BlockSize = 128,
                Mode = CipherMode.CBC,
                Padding = PaddingMode.Zeros,
                KeySize = 128
            };
            _RijndaelManaged.GenerateKey();
            _RijndaelManaged.GenerateIV();
        }

        public HEncryption(byte[] key) : this()
        {
            XorBytes(key, 1, key.Length - 1, key[0]);
            int byteCount = KeySize >> 1;
            _RijndaelManaged.Key = key.Take(byteCount).ToArray();
            _RijndaelManaged.IV = key.Skip(byteCount).ToArray();
        }

        /// <summary>
        /// 加密并压缩内容
        /// </summary>
        /// <param name="bytes">希望加密并压缩的内容</param>
        /// <param name="startIndex">开始加密的下标位置</param>
        /// <param name="count">加密的长度</param>
        /// <returns>加密结果</returns>
        public byte[] Encode(byte[] bytes, int startIndex, int count)
        {
            byte[] compressed = Compress(bytes, startIndex, count);
            return _RijndaelManaged.CreateEncryptor().TransformFinalBlock(compressed, 0, compressed.Length);
        }

        /// <summary>
        /// 解密内容
        /// </summary>
        /// <param name="bytes">希望解密密并压缩的内容</param>
        /// <param name="startIndex">开始解密的下标位置</param>
        /// <param name="count">解密的长度</param>
        /// <returns>解密结果</returns>
        public byte[] Decode(byte[] bytes, int startIndex, int count)
        {
            byte[] decrypted = _RijndaelManaged.CreateDecryptor().TransformFinalBlock(bytes, startIndex, count);
            return Decompress(decrypted, 0, decrypted.Length);
        }


        /// <summary>
        /// 压缩数据
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private static byte[] Compress(byte[] bytes, int startIndex, int count)
        {
            using (MemoryStream compressStream = new MemoryStream())
            {
                using (GZipStream zipStream = new GZipStream(compressStream, CompressionMode.Compress))
                {
                    zipStream.Write(bytes, startIndex, count);
                }
                return compressStream.ToArray();
            }
        }

        /// <summary>
        /// 解压数据
        /// </summary>
        /// <param name="bytes"></param>
        /// <returns></returns>
        private static byte[] Decompress(byte[] bytes, int startIndex, int count)
        {
            using (MemoryStream compressStream = new MemoryStream(bytes, startIndex, count))
            {
                using (GZipStream zipStream = new GZipStream(compressStream, CompressionMode.Decompress))
                {
                    using (MemoryStream resultStream = new MemoryStream())
                    {
                        zipStream.CopyTo(resultStream);
                        return resultStream.ToArray();
                    }
                }
            }
        }
    }

}
